﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using XNOgre.Scene;

namespace XNOgre.Core
{
    public enum Projection
    {
        Orthographic,
        Perspective
    }
    public class Camera : Node
    {

        float aspectRatio;
        float fieldOfView;
        float farDist, nearDist;
        float orthoSize;
        Projection projType;
        Matrix projMatrix, viewMatrix;
        private static int numCameras = 0;

        private BoundingFrustum frustum;

        /// <summary>
        /// Creates a camera to view the scene. 
        /// Can't be created directly, must use SceneManager.CreateCamera()
        /// </summary>
        /// <param name="name"></param>
        internal Camera(string name = null)
        {
            this.projType = Projection.Perspective;
            this.fieldOfView = MathHelper.PiOver4;
            this.farDist = 100000.0f;
            this.nearDist = 100f;
            this.aspectRatio = 1.33333333333333f;
            this.orthoSize = 1000.0f;

            if (String.IsNullOrEmpty(name))
            {
                name = Root.GenerateName(typeof(Camera));
            }
            else
            {
                this.name = name;
            }

            UpdateProjection();
            UpdateView();

            frustum = new BoundingFrustum(this.viewMatrix * this.projMatrix);
            
        }
        protected void UpdateProjection()
        {
            if (projType == Projection.Perspective)
            {
                projMatrix = Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio, nearDist, farDist);
            }
            else
            {
                projMatrix = Matrix.CreateOrthographic(orthoSize, orthoSize, nearDist, farDist);
            }

            frustum = new BoundingFrustum(viewMatrix * projMatrix);
        }
        protected void UpdateView()
        {
            Vector3 positionToUse = (Parent != null) ? DerivedPosition : position;
            Quaternion quatToUse = (Parent != null) ? DerivedOrientation : orientation;

            viewMatrix = Matrix.Invert(Matrix.CreateFromQuaternion(quatToUse) * Matrix.CreateTranslation(positionToUse));
            frustum = new BoundingFrustum(viewMatrix * projMatrix);
        }

        public float FieldOfView
        {
            get { return fieldOfView; }
            set
            {
                bool different = (fieldOfView != value);
                if (different)
                {
                    fieldOfView = value;
                    UpdateProjection();
                }
            }
        }
        public void LookAt(Vector3 location)
        {
            this.Direction = location - Position;
        }
        public float AspectRatio
        {
            get { return aspectRatio; }
            set
            {
                bool different = (aspectRatio != value);

                if (different)
                {
                    aspectRatio = value;
                    UpdateProjection();
                }
            }
        }
        public float FarClipDistance
        {
            get { return farDist; }
            set
            {
                bool differnt = (farDist != value);
                if (differnt)
                {
                    farDist = value;
                    UpdateProjection();
                }
            }
        }
        public float OrthoSize
        {
            get { return orthoSize; }
            set
            {
                bool different = (orthoSize != value);
                if (different)
                {
                    orthoSize = value;
                    UpdateProjection();
                }
            }
        }
        public float NearClipDistance
        {
            get { return nearDist; }
            set
            {
                bool different = (nearDist != value);
                if (different)
                {
                    nearDist = value;
                    UpdateProjection();
                }
            }
        }
        public Vector3 Direction
        {
            get
            {
                return Vector3.Transform(Vector3.Forward, Orientation);
            }
            set
            {
                if (value == Vector3.Zero)
                    return;

                Vector3 zAdjuctVec = -value;
                zAdjuctVec.Normalize();

                Quaternion targetWorldOrientation;


                Vector3[] axes = new Vector3[3];
                Orientation.ToAxes(out axes);
                Quaternion rotQuat;

                if ((axes[2] + zAdjuctVec).LengthSquared() < 0.00005f)
                {
                    // Oops, a 180 degree turn (infinite possible rotation axes)
                    // Default to yaw i.e. use current UP
                    rotQuat = axes[2].RotationTo(zAdjuctVec,this.Up);
                }
                else
                {
                    rotQuat = axes[2].RotationTo(zAdjuctVec);
                }
                targetWorldOrientation = rotQuat * Orientation;


                Orientation = targetWorldOrientation;


                UpdateView();
            }
        }
        protected override void UpdateFromParentImpl()
        {
            base.UpdateFromParentImpl();
            UpdateView();
        }
        public override Vector3 Position
        {
            get
            {
                return base.Position;
            }
            set
            {
                bool different = (base.Position != value);
                if (different)
                {
                    base.Position = value;
                    UpdateView();
                }
            }
        }
        public override Quaternion Orientation
        {
            get
            {
                return base.Orientation;
            }
            set
            {
                bool different = (base.Orientation != value);
                if (different)
                {
                    base.Orientation = value;
                    UpdateView();
                }
            }
        }
        public Vector3 Up
        {
            get
            {
                return Vector3.Transform(Vector3.UnitY, Orientation);
            }
        }
        public Matrix View
        {
            get
            {
                return viewMatrix;
            }
        }

        public Matrix Proj
        {
            get
            {
                return projMatrix;
            }
        }
        public BoundingFrustum Frustum
        {
            get { return frustum; }
        }

        internal SceneManager SceneManager { get; set; }

        internal void RenderScene(Graphics.ViewportTarget vpt)
        {
            SceneManager.RenderScene(this, vpt, vpt.ShowOverlays);
        }
    }
}
